package com.lvcoding.sec3.secdemo3.config;

import com.lvcoding.sec3.secdemo3.security.JwtLoginFilter;
import com.lvcoding.sec3.secdemo3.security.LoginFailHandler;
import com.lvcoding.sec3.secdemo3.security.LoginSuccessHandler;
import com.lvcoding.sec3.secdemo3.security.TokenFilter;
import com.lvcoding.sec3.secdemo3.security.sms.SmsAuthenticationFilter;
import com.lvcoding.sec3.secdemo3.security.sms.SmsAuthenticationProvider;
import com.lvcoding.sec3.secdemo3.security.sms.SmsCheckFilter;
import com.lvcoding.sec3.secdemo3.security.sms.SmsUserDetailService;
import com.lvcoding.sec3.secdemo3.util.ResponseUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @description 描述
 * @date   2022-02-28 下午5:39
 * @author  wuyanshen
 */
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final JwtLoginFilter jwtLoginFilter;

    private final TokenFilter tokenFilter;

    private final LoginFailHandler loginFailHandler;

    private final LoginSuccessHandler loginSuccessHandler;

    private final SmsUserDetailService smsUserDetailService;


    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        // web.ignoring().antMatchers("/css/**","/swagger-ui.html", "/favicon.ico");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        // 短信验证码认证提供者
        SmsAuthenticationProvider smsAuthenticationProvider = new SmsAuthenticationProvider();
        smsAuthenticationProvider.setUserDetailsService(smsUserDetailService);
        auth.authenticationProvider(smsAuthenticationProvider);

        // jwt登录认证提供者
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsServiceBean());
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        auth.authenticationProvider(daoAuthenticationProvider);

    }


    // 资源白名单
    private  static  final  String[]  AUTH_WHITELIST  =  {
            // -- swagger ui
            "/swagger-resources/**",
            "/swagger-ui/**",
            "/v3/api-docs",
            "/webjars/**",

            // -- 静态资源
            "/css/**",
            "/favicon.ico",

            // -- 接口api
            "/login/**",
            "/code/**",
            "/auth/**"
    };

    @Override
    public UserDetailsService userDetailsServiceBean() throws Exception {
        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
        inMemoryUserDetailsManager
                .createUser(User.withUsername("admin")
                        .password(passwordEncoder().encode("admin"))
                        .authorities("cup:user:add","cup:user:update","cup:user:delete")
                        .build());
        return inMemoryUserDetailsManager;
    }


    /**
     * 短信验证登录过滤器
     */
    public SmsAuthenticationFilter smsAuthenticationFilter() throws Exception {
        SmsAuthenticationFilter smsAuthenticationFilter = new SmsAuthenticationFilter();
        smsAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
        smsAuthenticationFilter.setAuthenticationSuccessHandler(loginSuccessHandler);
        smsAuthenticationFilter.setAuthenticationFailureHandler(loginFailHandler);
        return smsAuthenticationFilter;
    }

    /**
     * 短信验证码校验过滤器
     */
    public SmsCheckFilter smsCheckFilter() throws ServletException {
        SmsCheckFilter smsCheckFilter = new SmsCheckFilter();
        smsCheckFilter.setLoginFailHandler(loginFailHandler);
        smsCheckFilter.afterPropertiesSet();
        return smsCheckFilter;
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterBefore(tokenFilter, JwtLoginFilter.class)
                .addFilterBefore(smsCheckFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilterAt(jwtLoginFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(smsAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .csrf().disable()
                .authorizeRequests()
                .antMatchers(AUTH_WHITELIST).permitAll()
                .antMatchers().permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .successHandler(loginSuccessHandler)
                .failureHandler(loginFailHandler)
                .and()
                .logout()
                .and()
                .exceptionHandling()
                /**
                 * 授权异常类
                 * 通常是服务已经识别了请求，已经认证过了，但是对请求的资源没有权限，触发此异常
                 * 返回403
                 *
                 * @author wuyanshen
                 */
                .accessDeniedHandler(new AccessDeniedHandler() {
                    @Override
                    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
                        response.setStatus(HttpStatus.FORBIDDEN.value());
                        ResponseUtil.JsonResult(response, 403,"拒绝访问", null);
                    }
                })
                /**
                 * 自定义认证异常类
                 * <p>
                 * 通常是请求了需要认证的资源，但是没有认证成功，会触发认证异常
                 * 返回401
                 *
                 * @author wuyanshen
                 */
                .authenticationEntryPoint(new AuthenticationEntryPoint() {
                    @Override
                    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
                        response.setStatus(HttpStatus.UNAUTHORIZED.value());
                        ResponseUtil.JsonResult(response, 401,"未授权的访问", null);
                    }
                })
                .and()
                // .apply(smsAuthenticationSecurityConfig)
                //   .and()
                  .sessionManagement()
                  .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}
